Onchange & Compute

  • Step 1:

    onchange

    Use the onchange decorator if we want the value of some field change automatically when the value of another field is changed by the user. For example, let’s assume we have 3 fields like this.

    
                       from odoo import api, fields, models, _
     
     class MyModel(models.Model):
         _name = 'my.model'
      
         field_1 = fields.Integer(string='Field 1')
         field_2 = fields.Integer(string='Field 2')
         result = fields.Integer(string='Result')
    

    If we want the value of the result field to change automatically if the value of field_1 or field_2 is changed by the user, we can use the onchange decorator like below.

    
    @api.onchange('field_1', 'field_2')
    def calculate_result(self):
        self.result = self.field_1 + self.field_2
    

    While the program is running, if value one of the fields that we used as the trigger is changed by the user, the method, in this example the calculate_result method will be executed automatically.

    As mentioned earlier, field that we calculated with the onchange decorator still can be changed by the user manually, so we may need to add the readonly attribute to protect it. However, field that we marked with the readonly attribute, its value will not be stored in the database if we click the Save button. If you want its value to be stored, make sure to use the force_save attribute as shown below.

    
    <field name="result" readonly="1" force_save="1" />
    

    compute

    Furthermore, we can also use the compute field parameter to calculate the value of a field automatically. As show in the code below.

    
    from odoo import api, fields, models, _
     
     class MyModel(models.Model):
         _name = 'my.model'
      
         field_1 = fields.Integer(string='Field 1')
         field_2 = fields.Integer(string='Field 2')
         result = fields.Integer(string='Result', compute='compute_result')
          
         def compute_result(self):
             for rec in self:
                 rec.result = rec.field_1 + rec.field_2
    
             

    Unlike the onchange decorator, when we use the compute field parameter, make sure to loop the self parameter. Because if not, there will be an expected singleton error if there are a lot of data in the model. So avoid using code like below.

    
    result = fields.Integer(string='Result', compute='compute_result')
         
         def compute_result(self):
             self.result = self.field_1 + self.field_2
    
             

    Field that we marked with the compute field parameter will automatically to be a readonly field. So that there is no possibility for the user to change the field value from the frontend.

    Field that we marked with the compute field parameter, its value will not change until the form is saved. While still in create or edit mode, the field value will not change because the compute method has not been executed.

    The compute method is only executed on read action. It is when we open the tree view or form view, but not in create or edit mode. After we click the Save button, and we exit the create or edit mode, the value will change automatically, as shown below.

    The compute field parameter also give an advantage, if we change one of the fields that are used as the basis for calculation from the backend, with python code from any model or from the database via SQL command, the value will always change if we open the form next time.

    From the two images above, it can be seen that the value of result changes even though the value of field_1 is changed from the database via SQL commands. This is not possible if we use the onchange decorator.

    But if we want that the value of the result field to change immediately without having to click the save button, you can also use the depends decorator as below.

    
    from odoo import api, fields, models, _
     
     class MyModel(models.Model):
         _name = 'my.model'
      
         field_1 = fields.Integer(string='Field 1')
         field_2 = fields.Integer(string='Field 2')
         result = fields.Integer(string='Result', compute='compute_result')
          
         @api.depends('field_1', 'field_2')
         def compute_result(self):
             for rec in self:
                 rec.result = rec.field_1 + rec.field_2
    
             

    Field that we marked with the compute field parameter, by default, will not be translated as a column in the database table. So it is not possible for us to select this field in a SQL command.

    From the picture above, you can see that there is no column named result. If we want this field to be translated into a column in the database table, we must add the store field parameter with the value of True, as shown below

    
             result = fields.Integer(string='Result', compute='compute_result', store=True)